<?php

namespace App\Http\Controllers;

//use Elastic\Elasticsearch\ClientBuilder;
use Elasticsearch\ClientBuilder;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

/**
 *
 * composer require elasticsearch/elasticsearch 7.* 不安装最新版本
 *
 * Elasticsearch 服务
 */
class ElasticsearchServiceController
{

    /**
     * 获取对象
     */
    public function getEsServiceObj()
    {
        $client = ClientBuilder::create()->setHosts(
            [
                'host'   => config('elasticsearch.host'),
                'port'   => config('elasticsearch.port'),
                'scheme' => config('elasticsearch.scheme'), //  http  数据 但是需要账号密码验证
                // 'user' => config('elasticsearch.user'), // 如果需要的话
                //'pass' => config('elasticsearch.pass'), // 如果需要的话
            ]
        )
            //  ->setBasicAuthentication(config('elasticsearch.user'), config('elasticsearch.pass'))  // 如果是 https ，需要账号密码需要在这里配置，然后还需要配置证书，否则会存在问题
            ->build();
        return $client;
    }

    /**
     * 判断es中是否有数据
     * @param guid
     */
    public function esHasData($index_name, $guid)
    {
        $params = [
            'index' => $index_name,
            'body' => [
                'query' => [
                    'bool' => [
                        'must' => [], // 强制匹配的条件
                        'filter' => [], // 不影响评分，只过滤的条件
                    ],
                ],
            ],
        ];
        // 添加多条件精确检索
        if ($guid) {
            $params['body']['query']['bool']['must'][] = [
                'term' => [
                    'guid' => $guid,
                ],
            ];
        }
        // 可以继续添加更多条件...
        $response = $this->getEsServiceObj()->search($params);
        // 处理响应数据
        $results = $response['hits']['hits'];
        return $results;
    }

    /**
     * 判断es中是否有数据
     * @param guid
     */
    public function getSearchEsTotal($index_name, $keywords)
    {
        $params = [
            'index' => $index_name,
            'body' => [
                'query' => [
                    'bool' => [
                        'must' => [], // 强制匹配的条件
                        'filter' => [], // 不影响评分，只过滤的条件
                    ],
                ],
                'track_total_hits' => true, // 确保设置为 true 以获取总数  不加此参数，数据大于 10000  不显示总数量，只会显示 大于 10000
            ],
        ];
        // 添加多条件精确检索
        if ($keywords) {
            $params['body']['query']['bool']['must'][] = [
                'term' => [
                    'title' => $keywords,
                ],
            ];
        }
        // 可以继续添加更多条件...
        $response = $this->getEsServiceObj()->search($params);
        // 处理响应数据
        $results = $response['hits']['total']['value']; //返回总数据量
        return '总数据量: ' . $results;
    }

    /**
     * 添加数据
     */
    public function esDataInsert($index_name, $data)
    {
        //拼接数据
        $params = [
            'index' => $index_name,
            'type' => '_doc',
            'id' => $data['id'],
            'body' => $data
        ];
        //存储到es中
        $response = $this->getEsServiceObj()->index($params);
        return $response;
    }

    /**
     * 删除es数据(自动删除索引)
     */
    public function esDataDelete($index_name, $id)
    {
        $params = [
            'index' => $index_name, //索引名
            'type' => '_doc',
            'id' => $id, //具体数据
        ];
        $response = $this->getEsServiceObj()->delete($params);
        return $response;
    }
    /**
     * 生成es索引 （如果没有特殊需求，在创建数据时，会自动创建索引，如果有特殊需求，则需要先创建索引，在加数据）
     * @param index 索引名称
     */
    public function esCreateIndex($index = 'title')
    {
        // 创建索引
        $params = [
            'index' => $index, //索引的名称（mysql的表名）   也可以先不创建索引，添加数据时，会自动创建索引，有特殊需求可以先创建索引
            'body' => [
                'settings' => [
                    'number_of_shards' => 3, //设置数据的分片数，默认为5
                    'number_of_replicas' => 0 //备份数，单个es服务器无需备份
                ],
                'mappings' => [
                    '_source' => [
                        'enabled' => true
                    ],
                    // 'id' => [
                    //     'type' => 'integer'
                    // ],
                    //下面是配置字段类型
                    'properties' => [
                        'id' => [ //password 字段名
                            'type' => 'integer', //字段类型
                        ],
                        //  username 字段
                        'title' => [
                            'type' => 'text',  //text类型，做模糊搜索用
                            "analyzer" => "ik_max_word", //较细粒度分词，写入数据用  需要安装ik并将ik放入es的plugin中
                            "search_analyzer" => "ik_max_word", //较细粒度分词，查询数据用  需要安装ik并将ik放入es的plugins中  ik_smart 提取粒度较粗，而后者 ik_max_word 则较细，它给出更多的 token。
                            "index" => true  // 默认情况下，text类型的字段会被索引
                        ],
                        'guid' => [
                            'type' => 'text', //字段类型
                        ],
                        'big_type_id' => [
                            'type' => 'integer', //字段类型
                        ],
                        'type_id' => [
                            'type' => 'integer', //字段类型
                        ],
                        'resource_business_id' => [
                            'type' => 'integer', //字段类型
                        ],
                        'author' => [
                            'type' => 'text', //字段类型
                            //    "doc_values" => false  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                        ],
                        'isbn' => [
                            'type' => 'text', //字段类型
                            //  "doc_values" => false  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                        ],
                        'press' => [
                            'type' => 'text', //字段类型
                            //     "doc_values" => false,  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                            "index" => false  // 禁用索引
                        ],
                        'pre_time' => [
                            'type' => 'text', //字段类型
                            //    "doc_values" => false,  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                            "index" => false  // 禁用索引
                        ],
                        'img' => [
                            'type' => 'text', //字段类型
                            //    "doc_values" => false,  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                            "index" => false  // 禁用索引
                        ],
                        'intro' => [
                            'type' => 'text', //字段类型
                            //   "doc_values" => false  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                        ],
                        'create_time' => [
                            'type' => 'date', //字段类型
                            'format' => 'yyyy-MM-dd\'T\'HH:mm:ssX||epoch_millis',
                            //    "doc_values" => false,  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                            "index" => false  // 禁用索引
                        ],
                        'change_time' => [
                            'type' => 'date', //字段类型
                            'format' => 'yyyy-MM-dd\'T\'HH:mm:ssX||epoch_millis',
                            //   "doc_values" => false,  //-- 关闭不需要字段的doc_values功能，仅对需要排序，汇聚功能的字段开启。
                            "index" => false  // 禁用索引
                        ],
                        // 'create_time' => [
                        //     'type' => 'keyword' //keyword类型 做精确搜索用
                        // ],
                    ]
                ]
            ]
        ];
        try {
            $response = $this->getEsServiceObj()->indices()->create($params);

            if ($response['acknowledged'] === true && $response['shards_acknowledged'] === true) {
                return true; //索引创建成功
            } else {
                return false; //索引创建失败
            }
        } catch (Exception $e) {
            //dump($e->getMessage());        die;
            return $e->getMessage(); //
        }
        //return $response;
        /**
         * [
            "acknowledged" => true
            "shards_acknowledged" => true
            "index" => "cqst_resource"
            ]
         */
    }



    /**
     * 搜索内容
     * @param keywords
     *
     * $page 页数   传 null 则不需要分页
     * $limit 每页显示数量
     *
     * term：完全匹配，搜索之前不会对搜索的关键字进行分词，直接拿关键字去文档分词库中的去匹配内容
            terms：和term的查询机制是一样，区别是可以去多个Field字段中匹配
            match：实际底层就是多个term查询，将多个term查询的结果给你封装到了一起
            match_all：查出全部内容，不指定任何查询条件。
            boolean match：基于一个Field匹配的内容，采用 and或 or 的方式 连接
            multi_match：match 针对一个field做检索，multi_match 针对多个 field 进行检索。多个 field 对应一个text
            id: 直接根据ID获取
            ids: 根据多个ID查询，类似MySQL中的 where id in (1,2,3)
            prefix：前缀查询，可以通过一个关键字去指定一个Field的前缀，从而查询到指定的文档
            fuzzy： 模糊查询，我们输入字符的大概，ES就可以 （天天凯心）可以有错别字
            wildcard：通配查询，和MySQL中的 like 差不多，可以在查询时，在字符串中指定通配符 * 和占位符？
            range：范围查询，只针对数值类型，对某一个Field进行大于或小于的范围指定查询
            regexp: 正则查询，通过你编写的正则表达式去匹配内容

     */
    public function esDataSearch($index, $keywords, $page = 1, $limit = 100)
    {
        $params = [
            'index' => $index,
            //  'type' => '_doc',//不能传这个参数
            'body' => [
                // '_source'=>[
                //     'includes'=>['id','title'],//固定需要返回的字段，不填则表示所有
                // ],
                'track_total_hits' => true, // 确保设置为 true 以获取总数  不加此参数，数据大于 10000  不显示总数量，只会显示 大于 10000
                'query' => [
                    // 'multi_match' => [  //从多个字段中获取数据
                    //     'query' => $keywords,
                    //     'fields' => ['title', 'intro']
                    // ]
                    'match' => [
                        'title' => $keywords,  //如果是多级，采用点的方式
                        // 'body.title' => $keywords,  //如果是多级，采用点的方式
                        //     'fields' => ['title', 'intro']
                    ]
                ],
                //搜索数据高亮显示
                'highlight' => [
                    'fields' => [
                        'title' => [
                            'pre_tags' => [
                                '<span style="color: red">'
                            ],
                            'post_tags' => [
                                '</span>'
                            ]
                        ]
                    ]
                ]
            ]
        ];

        // 如果需要分页，添加以下参数
        if ($page) {
            $params['body']['from'] = $page - 1; // 起始页，0为第一页
            $params['body']['size'] = $limit; // 每页显示数量
        }
        $results = $this->getEsServiceObj()->search($params);
        // $results = array_column($results['hits']['hits'], '_source');
        $data = [];
        foreach ($results['hits']['hits'] as $val) {
            $val['_source']['highlight_title'] = $val['highlight']['title'][0];
            $data['data'][] = $val['_source'];
        }
        $data['total'] = $results['hits']['total']['value']; //返回总数据量
        $data['current_page'] = $page; //返回总数据量
        $data['last_page'] = ceil($data['total'] / $limit); //最后一页
        $data['per_page'] = $limit; //每页数量
        return $data;
    }

    /*********************************************************************************************************************************************************************************/



    // 批量添加文档
    public function bulk_doc($docs, $index_name = 'title')
    {
        $params = ['body' => []];
        $res = 0;

        foreach ($docs as  $i => $item) {
            if (!isset($item['id']) || empty($item['id'])) {
                continue;
            }

            $params['body'][] = [
                'index' => [
                    '_index' => $index_name,
                    '_id' => $item['id']
                ],
            ];
            unset($item['id']);
            $params['body'][] = $item;

            if ($i % 1000 == 0) {
                $responses = $this->getEsServiceObj()->bulk($params);
                $params = ['body' => []];
                unset($responses);
            }
            $res++;
        }
        dump($params);
        die;
        if (!empty($params['body'])) {
            $responses = $this->getEsServiceObj()->bulk($params);
        }

        return $res;
    }


    // 批量添加文档
    public function bulk_doc2($docs, $index_name = 'title')
    {
        $params = [];
        $res = 0;

        foreach ($docs as  $i => $item) {
            if (!isset($item['id']) || empty($item['id'])) {
                continue;
            }

            $params[] = [
                'index' => [
                    '_index' => $index_name,
                    '_id' => $item['id']
                ],
            ];
            unset($item['id']);
            $params[] = $item;
            dump($i);
            dump($i % 1000);
            die;
            if ($i % 1000 == 0) {
                dump(1);
                $responses = $this->getEsServiceObj()->bulk($params);
                $params = [];
                unset($responses);
            }
            $res++;
        }
        dump($params);
        die;
        if (!empty($params)) {
            $responses = $this->getEsServiceObj()->bulk($params);
        }
        return $res;
    }

    /**
     * 修改数据
     */
    public function esChange()
    {
        $data = [
            'id' => 2,
            'username' => '小方啊',
            'password' => '222222',
        ];
        $params = [
            'index' => 'test',
            'id' => $data['id'],
            'body' => [
                'username' => $data['username'],
                'password' => $data['password'],
            ],
        ];
        $response = $this->getEsServiceObj()->index($params);
        return $response;
    }



    /**
     * 搜索内容
     * @param keywords
     *
     * $page 页数   传 null 则不需要分页
     * $limit 每页显示数量
     *
     * term：完全匹配，搜索之前不会对搜索的关键字进行分词，直接拿关键字去文档分词库中的去匹配内容
            terms：和term的查询机制是一样，区别是可以去多个Field字段中匹配
            match：实际底层就是多个term查询，将多个term查询的结果给你封装到了一起
            match_all：查出全部内容，不指定任何查询条件。
            boolean match：基于一个Field匹配的内容，采用 and或 or 的方式 连接
            multi_match：match 针对一个field做检索，multi_match 针对多个 field 进行检索。多个 field 对应一个text
            id: 直接根据ID获取
            ids: 根据多个ID查询，类似MySQL中的 where id in (1,2,3)
            prefix：前缀查询，可以通过一个关键字去指定一个Field的前缀，从而查询到指定的文档
            fuzzy： 模糊查询，我们输入字符的大概，ES就可以 （天天凯心）可以有错别字
            wildcard：通配查询，和MySQL中的 like 差不多，可以在查询时，在字符串中指定通配符 * 和占位符？
            range：范围查询，只针对数值类型，对某一个Field进行大于或小于的范围指定查询
            regexp: 正则查询，通过你编写的正则表达式去匹配内容

     */
    public function esSearch($index, $keywords, $page = 1, $limit = 100)
    {
        $params = [
            'index' => $index,
            //  'type' => '_doc',//不能传这个参数
            'body' => [
                'query' => [
                    // 'multi_match' => [  //从多个字段中获取数据
                    //     'query' => $keywords,
                    //     'fields' => ['title', 'intro']
                    // ]
                    'match' => [
                        'body.title' => $keywords,  //如果是多级，采用点的方式
                        //     'fields' => ['title', 'intro']
                    ]
                ],
                //搜索数据高亮显示
                'highlight' => [
                    'fields' => [
                        'body.title' => [
                            'pre_tags' => [
                                '<span style="color: red">'
                            ],
                            'post_tags' => [
                                '</span>'
                            ]
                        ]
                    ]
                ]
            ]
        ];

        // 如果需要分页，添加以下参数
        if ($page) {
            $params['body']['from'] = $page - 1; // 起始页，0为第一页
            $params['body']['size'] = $limit; // 每页显示数量
        }

        $results = $this->getEsServiceObj()->search($params);
        dump($params);
        dump($results);
        die;
        $results = array_column($results['hits']['hits'], '_source');
        return $results;
    }

    /**
     * 检索数据2
     */
    public function esSearch2($keywords)
    {
        $esWhere = [];
        //如果需要 同时满足多个查询条件（and查询），则复制下面参数，改title为 descs即可
        $esWhere['bool']['must'][] = [
            'match_phrase' => [
                'title' => $keywords
            ]
        ];

        /*
            $esWhere['bool']['must'][] = [
                'match_phrase' => [
                    'descs' =>$keywords
                ]
            ];
            */

        //按id排序
        $sortOrder = ['id' => 'asc'];

        $where = [
            'index' => 'article',
            'size' => 10,
            'from' => 0,
            'type' => '_doc',
            'body' => [
                'query' => $esWhere, //查询条件
                'sort' => $sortOrder, //排序条件，注意 添加后 返回结果 无_score值
                'highlight' => [ //将查询的数据进行高亮显示
                    'fields' => [
                        'title' => [
                            'pre_tags' => ['<span style="color: red">'],
                            'post_tags' => ['</span>'],
                        ]
                    ]
                ]
            ]
        ];

        $rs = $this->getEsServiceObj()->search($where);
        return $rs;
    }
    /**
     * 删除es数据(自动删除索引)
     */
    public function esDelete()
    {
        $data = [
            'id' => 2,
            'username' => '小方啊',
            'password' => '222222',
        ];
        $params = [
            'index' => 'test', //索引名
            'type' => '_doc',
            'id' => $data['id'], //具体数据
        ];
        $response = $this->getEsServiceObj()->delete($params);
        return $response;
    }
    /**
     * 删除索引
     */
    public function deleteIndex($index_name)
    {
        $client = $this->getEsServiceObj();
        $client->indices()->delete(['index' => $index_name]);
    }




    // 判断文档存在
    public function exists_doc($id, $index_name = 'meitian')
    {
        $params = [
            'index' => $index_name,
            'id' => $id
        ];

        $response = $this->getEsServiceObj()->exists($params);
        return $response;
    }
    // 获取文档
    public function get_doc($id, $index_name = 'meitian')
    {
        $params = [
            'index' => $index_name,
            'id' => $id
        ];

        $response = $this->getEsServiceObj()->get($params);
        return $response;
    }

    // 更新文档
    public function update_doc($id, $doc, $index_name = 'meitian')
    {
        $params = [
            'index' => $index_name,
            'id' => $id,
            'body' => [
                'doc' => $doc
            ]
        ];

        $response = $this->getEsServiceObj()->update($params);
        return $response;
    }
    // 删除文档
    public function delete_doc($id, $index_name = 'meitian')
    {
        $params = [
            'index' => $index_name,
            'id' => $id
        ];

        $response = $this->getEsServiceObj()->delete($params);
        return $response;
    }

    //查看映射
    public function get_mapping($index_name = "meitian")
    {
        $params = [
            'index' => $index_name,
        ];
        $response = $this->getEsServiceObj()->indices()->getMapping($params);
        return $response;
    }
}
